#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <functional>
#include <sys/epoll.h>
#include "err.hpp"
#include "log.hpp"
#include "sock.hpp"

namespace epoll_ns
{
    static const int defaultport = 8888;
    static const int size = 120;
    static const int defaultvalue = -1;
    static const int defaultnum = 64;

    using func_t = std::function<std::string(const std::string)>;

    class EpollServer
    {
    public:
        EpollServer(func_t f, uint16_t port = defaultport, int num = defaultnum)
            : func_(f), _port(port), _num(num), _revs(nullptr), _listensock(defaultvalue), _epfd(defaultport)
        {
        }

        void initServer()
        {
            // 1. 创建 socket
            _listensock = Sock::Socket();
            Sock::Bind(_listensock, _port);
            Sock::Listen(_listensock);
            // 2. 创建 epoll 模型
            _epfd = epoll_create(size);
            if (_epfd < 0)
            {
                logMessage(FATAL, "epoll create error: %s", strerror(errno));
                exit(EPOLL_CREATE_ERR);
            }
            // 3. 添加 _listensock 到 epoll 模型中去
            //    typedef union epoll_data {
            //        void        *ptr;
            //        int          fd;
            //        uint32_t     u32;
            //        uint64_t     u64;
            //    } epoll_data_t;
            //    struct epoll_event {
            //        uint32_t     events;      /* Epoll events */
            //        epoll_data_t data;        /* User data variable */
            //    };

            struct epoll_event ev;
            ev.events = EPOLLIN;
            ev.data.fd = _listensock; // 当事件就绪，被重新捞取上来的时候，我们要知道是哪一个 fd 就绪了！
            epoll_ctl(_epfd, EPOLL_CTL_ADD, _listensock, &ev);

            // 4. 申请就绪事件的空间
            _revs = new struct epoll_event[_num];

            logMessage(NORMAL, "init server success");
        }

        // 处理 readyNum 个就绪的事件(当然得判断是什么事件就绪了)
        void HandlerEvent(int readyNum)
        {
            logMessage(DEBUG, "HandlerEvent in");
            for (int i = 0; i < readyNum; i++)
            {
                uint32_t events = _revs[i].events;
                int sock = _revs[i].data.fd;

                // 判断就绪的 sock 的类型
                if (sock == _listensock && (events & EPOLLIN))
                {
                    // _listensock 监听事件就绪了，获取新连接
                    std::string clientip;
                    uint16_t clientport;
                    int fd = Sock::Accept(_listensock, &clientip, &clientport);
                    if (fd < 0)
                    {
                        logMessage(WARNING, "accept error");
                        continue; // 继续去获取啊
                    }
                    // 获取 fd 成功，可以直接读取吗？  不可以的，得放入 epoll 中去的哦
                    struct epoll_event ev;
                    ev.events = EPOLLIN;
                    ev.data.fd = fd;
                    epoll_ctl(_epfd, EPOLL_CTL_ADD, fd, &ev);
                }
                else if (events & EPOLLIN)
                {
                    // 普通的都事件就绪(读事件就绪)
                    // 依旧有问题
                    char buffer[1024];
                    // 把本轮数据读完，就一定能够读到一个完整的请求吗？
                    int n = recv(sock, buffer, sizeof(buffer), 0);
                    if (n > 0)
                    {
                        buffer[n] = 0;
                        logMessage(DEBUG, "client# %s", buffer);
                        // todo
                        std::string response = func_(buffer);
                        send(sock, response.c_str(), response.size(), 0);
                    }
                    else if (n == 0)
                    {
                        // 建议先从 epoll 移除，才 close fd
                        epoll_ctl(_epfd, EPOLL_CTL_DEL, sock, nullptr);
                        close(sock);
                        logMessage(NORMAL, "client quit");
                    }
                    else
                    {
                        // 建议先从 epoll 移除，才 close fd
                        epoll_ctl(_epfd, EPOLL_CTL_DEL, sock, nullptr);
                        close(sock);
                        logMessage(ERROR, "recv error, code: %d, errstring: %s", errno, strerror(errno));
                    }
                }
                else
                {
                    // 写事件就绪
                }
            }
            logMessage(DEBUG, "HandlerEvent out");
        }

        void start()
        {
            // int timeout = 3000; // -1:阻塞式  0:非阻塞式  >0:每隔一段时间询问一次(好像和select里面的不一样的)
            int timeout = -1;
            for (;;)
            {
                // 有多少个事件就绪了
                int n = epoll_wait(_epfd, _revs, _num, timeout);
                switch (n)
                {
                case 0:
                    logMessage(NORMAL, "timeout ...");
                    break;
                case -1:
                    logMessage(WARNING, "epoll_watit failed, code: %d, errstring: %s", errno, strerror(errno));
                    break;
                default:
                    logMessage(NORMAL, "have event ready");
                    // 什么叫做事件就绪？
                    // 底层的IO满足了，可以进行某种IO行为了，就叫做事件就绪
                    // select/poll/epoll ---> 等 ---> IO就绪事件的通知机制

                    // 通知机制有没有策略呢？
                    // （LT: 水平触发工作模式   ET: 边缘触发工作模式）
                    // 1. 默认是 LT 模式的：只要底层有数据还没有读取完毕的时候会一直通知你去读取。
                    // 2. 可以在 initServer 函数里面把 _listensock 设置进 epoll 里面的时候添加 EPOLLET 选项就设置成了 EPOLLET 模式了（没有 EPOLLLT 模式的因为默认就是 LT 模式的）
                    //    ET 模式：只要底层有数据没有读完，epoll 不再通知用户，除非底层的数据变化的时候(在此增多), 才会再通知你一次。
                    //    所以当有一个 telnet 127.0.0.1 8888 链接上来的话，没有下面的 HandlerEvent 函数去处理的话，会一直打印 have event ready 的。

                    // ET 模式：底层只有数据从无到有，从有到多变化的时候，才会通知（rb + cb + ready queue）上层 ---> 只会通知一次 ---> 倒逼程序员将本轮就绪的数据全部读取到上层
                    // ---> 你怎么知道你把本次就绪的底层的数据读取完毕了呢？---> 循环读取，知道读取不到数据了
                    // ---> 一般的 fd 是阻塞式的 fd ---> ET, 对应的 fd 必须是非阻塞的
                    // LT 模式：阻塞的，非阻塞也可以的
                    // LT 模式下，我们可不可以模仿 ET 的工作模式呢？ 是可以的

                    // 倒逼程序员将本轮就绪的数据全部读取到上层的理解：
                    // 1. 不仅仅体现在通知机制上
                    // 2. 尽快让上层把数据取走 ---> TCP 可以给发送方提供一个更大的窗口大小 ---> 让对方更新出更大的滑动窗口 ---> 提高底层的数据发送效率，更好的理由诸如tcp延迟应答策略等等

                    // tcp 中的 PSH 的作用？？  让底层数据就绪事件，再让上层知道

                    HandlerEvent(n);
                    break;
                }
            }
        }

        ~EpollServer()
        {
            if (_listensock != defaultport)
                close(_listensock);
            if (_epfd != defaultport)
                close(_epfd);
            if (_revs != nullptr)
                delete[] _revs;
        }

    private:
        // select 和 poll 都需要辅助数组，epoll 不需要，底层的红黑树就相当于是一个数组的功能
        uint16_t _port;
        int _listensock;
        int _epfd;
        struct epoll_event *_revs;
        int _num;
        func_t func_;
    };
}
